package gamefw.gameplugin;

import gamefw.framework.Coordinate;
import gamefw.framework.GameManager;


/**
 * The Class GameLogic implements the actual rules for a specific game.
 * It is responsible to implement the rules to select the player for the next turn,
 * to decide when the game is over and declare the winner. It also must define the number
 * players that are required for the particular game rules. 
 */
public abstract class AbstractGameLogic implements ISpecialEventListener {
	
	private GameManager gameManager;
	
	private GameBoard board;
	
	private AbstractPlayer[] players;
	
	private int currentPlayerIndex;
	
	public AbstractGameLogic() {
		this.currentPlayerIndex = -1;
	}
	
	/**
	 * Initialize the game logic if needed
	 */
	public abstract void initialize();
	
	/**
	 * Gets the game title (e.g., "Tic Tac Toe")
	 * @return the title of the game
	 */
	public abstract String getGameTitle();
	
	/**
	 * Gets the desired screen size.
	 * This can be ignored in the console-based ui plugin
	 * @return the desired size of the screen
	 */
	public abstract Coordinate getScreenSize();

	/**
	 * Gets the number of players that are required for this particular game.
	 *
	 * @return the number of players that are required for this particular game.
	 */
	public abstract int getPlayersNumber();

	/**
	 * Gets the player that has won the game or null if the game is not over.
	 *
	 * @return the winner of the game
	 */
	public abstract AbstractPlayer getWinner();

	/**
	 * Gets the player that should play the next turn.
	 *
	 * @return the next player's index
	 */
	public abstract int getNextPlayerIndex();

	/**
	 * Checks if the game is over.
	 *
	 * @return true, if is game over
	 */
	public abstract boolean isGameOver();
	
	/**
	 * Returns the list of initial game entities 
	 * @return the list of initial game entities
	 */
	public abstract String[] getInitialEntities();
	
	/**
	 * Main game loop should be implemented in this method.
	 */
	public final void gameLoop()
	throws Exception {
		// Skeleton of the game loop.
		while (!isGameOver()) {
			this.currentPlayerIndex = getNextPlayerIndex();
			AbstractPlayer player = getPlayer(getCurrentPlayerIndex());
			
			// Draw the updated state.
			display();

			// Let the player play the turn.
			PlayResult result = player.play(getBoard(), getGameManager().getInputManager());
			handlePlayResult(result);
		}
		
		// Draw the last board state.
		display();
	}

	private void display() {
		preDisplay();
		
		getGameManager().getOutputManager().beginDraw();
		
		displayBoard();
		
		getGameManager().getOutputManager().endDraw();
		
		postDisplay();
	}
	
	/**
	 * Display the board.
	 * This method can be overloaded if there are some more things to draw
	 * other than the board and the game entities.
	 */
	protected void displayBoard() {
		getBoard().display();
	}
	
	protected abstract void preDisplay();
	
	protected abstract void postDisplay();

	protected abstract void handlePlayResult(PlayResult result);

	/**
	 * Get game manager. Used to get the resource type, or to access the UI factory.
	 * 
	 * @return reference of the game manager
	 */
	protected GameManager getGameManager() {
		return gameManager;
	}

	/**
	 * Set game manager. Called by game manager itself
	 */
	public void setGameManager(GameManager gameManager) {
		this.gameManager = gameManager;
	}

	/**
	 * Get the game board
	 * 
	 * @return reference of the game board
	 */
	public GameBoard getBoard() {
		return board;
	}

	/**
	 * Set the game board
	 * 
	 * @param board reference of the game board
	 */
	public void setBoard(GameBoard board) {
		this.board = board;
	}

	/**
	 * Return the player of given index
	 * @param playerIndex index of the player
	 * @return reference of the player
	 */
	public AbstractPlayer getPlayer(int playerIndex) {
		return players[playerIndex];
	}

	/**
	 * Set the players. Called by the game manager
	 * @param players
	 */
	public void setPlayers(AbstractPlayer[] players) {
		this.players = players;
	}

	/**
	 * Get current player
	 * @return current player
	 */
	public int getCurrentPlayerIndex() {
		return currentPlayerIndex;
	}
}